<?php
/**
 * MySQL処理クラス
 * 
 * 1.0 2011.01.06 初期リリース
 * 
 * @filesource FTDB_Mysql.class.php
 * @package forty-two
 * @subpackage forty-two
 * @version 1.0
 */

/**
 * MySQL処理クラス
 * 
 * 1.0 2011.01.06 初期リリース
 * 
 * @author koto
 * @package forty-two
 * @version 1.0
 */
class FTDB_Mysql
{
    var $_link = array();
    
    
    var $_baseSQL = array(
                            'INSERT' => array(
                                'head'      => 'INSERT INTO #TABLENAME',
                                'middle'    => '(#COLUMNS)',
                                'tail'      => 'VALUES #VALUES',
                                'table'     => TRUE,
                                'where'     => TRUE,
                            ),
                            'UPDATE' => array(
                                'head'      => 'UPDATE #TABLENAME',
                                'middle'    => 'SET',
                                'tail'      => '#COLUMN=#VALUE',
                                'table'     => TRUE,
                                'where'     => TRUE,
                            ),
                            'DELETE' => array(
                                'head'      => 'DELETE FROM #TABLENAME',
                                'middle'    => NULL,
                                'tail'      => NULL,
                                'table'     => TRUE,
                                'where'     => TRUE,
                            ),
                            'DELETEALL' => array(
                                'head'      => 'DELETE FROM #TABLENAME',
                                'middle'    => NULL,
                                'tail'      => NULL,
                                'table'     => TRUE,
                                'where'     => NULL,
                            ),
                            'SELECT' => array(
                                'head'      => 'SELECT #GETCOLUMN',
                                'middle'    => 'FROM #TABLENAME',
                                'tail'      => NULL,
                                'table'     => TRUE,
                                'where'     => TRUE,
                            ),
                            'COUNT' => array(
                                'head'      => 'SELECT COUNT(#GETCOLUMN)',
                                'middle'    => 'FROM #TABLENAME',
                                'tail'      => NULL,
                                'table'     => TRUE,
                                'where'     => TRUE,
                            ),
                            'SETNAMES' => array(
                                'head'      => 'SET NAMES #PARAMETER',
                                'middle'    => NULL,
                                'tail'      => NULL,
                                'table'     => FALSE,
                                'where'     => FALSE,
                            ),
                        );
    
    var $_sqlEnd = ';';
    
    var $_charaSet = array(
                            'SJIS'      => 'sjis',
                            'EUC-JP'    => 'ujis',
                            'UTF-8'     => 'utf8',
                        );
    
    
    
    /**
     * ファクトリーメソッド
     *
     * エラーオブジェクトのインスタンスを作成します。
     * また、頻繁に呼び出される可能性があるので、メモリ削減と速度向上のために、
     * オブジェクトをグローバルで保持しポインタを返します。
     * 
     * @since 1.0
     * @static
     * @access public
     * @return object
     */
    static function & factory()
    {
        // 既に定義済みの場合は既存オブジェクトを返す
        if (isset($GLOBALS['forty-two']['FTDB_Mysql']['obj'])
             && is_object($GLOBALS['forty-two']['FTDB_Mysql']['obj'])
        ) {
            return $GLOBALS['forty-two']['FTDB_Mysql']['obj'];
        } else {
            // 自分を実体化
            $obj =& new FTDB_Mysql();
            $GLOBALS['forty-two']['FTDB_Mysql']['obj'] = $obj;
            return $GLOBALS['forty-two']['FTDB_Mysql']['obj'];
        }
        
        return FALSE;
    }
    
    
    
    /**
     * データ登録処理
     * 
     * データを登録します。
     * 
     * @access public
     * @return string
     */
    function insert()
    {
        
    }
    
    
    
    /**
     * データ更新処理
     * 
     * データを更新します。
     * 
     * @access public
     * @return string
     */
    function update()
    {
        
    }
    
    
    
    /**
     * データ削除処理
     * 
     * データを削除します。
     * 
     * @access public
     * @return string
     */
    function delete()
    {
        
    }
    
    
    
    /**
     * 全データ削除処理
     * 
     * 全てのデータを削除します。
     * 
     * @access public
     * @return string
     */
    function deleteAll()
    {
        
    }
    
    
    
    /**
     * データ1件取得処理
     * 
     * データを1件取得します。
     * 
     * @access public
     * @return string
     */
    function getRow()
    {
        
    }
    
    
    
    /**
     * データ取得処理
     * 
     * データを取得します。
     * 
     * @access public
     * @return string
     */
    function select($where = array(), $getColumn = array(), $db = DEFAULT_DB_CONNECTION_NAME, $tableName = NULL)
    {
        $where = NULL;
        
        // SQL生成
        $sql = $this->_buildSql('SELECT', array(), $tableName, $where);
        
        // SQL生成結果
        if ($sql == FALSE) {
            return FALSE;
        }
        
        // 実行
        $res = $this->query_unbuffered($sql, $db);
        
        // 実行結果
        if ($res == FALSE) {
            return FALSE;
        }
        
        // 実行結果取得
        $rows = array();
        while ($rows[] = call_user_func_array('mysql_fetch_assoc', array($res))) {
        }
        
        // リソース開放
        call_user_func_array('mysql_free_result', array($res));
        
        return $rows;
    }
    
    
    
    /**
     * データ件数取得処理
     * 
     * データ件数を取得します。
     * 
     * @access public
     * @return string
     */
    function count($where = array(), $db = DEFAULT_DB_CONNECTION_NAME, $tableName = NULL)
    {
        $where = NULL;
        
        // SQL生成
        $sql = $this->_buildSql('COUNT', array(), $tableName, $where);
        
        // SQL生成結果
        if ($sql == FALSE) {
            return FALSE;
        }
        
        // 実行
        $res = $this->query($sql, $db);
        
        // 実行結果
        if ($res == FALSE) {
            return FALSE;
        }
        
        // 実行結果取得
        $num = call_user_func_array('mysql_fetch_row', array($res));
        
        // リソース開放
        call_user_func_array('mysql_free_result', array($res));
        
        if (isset($num[0]) && is_numeric($num[0])) {
            $num = $num[0];
        } else {
            $num = FALSE;
        }
        
        return $num;
    }
    
    
    /**
     * 接続先DBの切り替え処理
     * 
     * 接続先のDBを切り替える処理を行います。
     * 複数のDBを同時に使う場合に有効です。
     * 
     * @access public
     * @return boolean
     */
    function switchDB()
    {
        
    }
    
    
    
    /**
     * SQLを実行
     * 
     * SQLを実行します。
     * 
     * @access public
     * @return string
     */
    function query($sql, $db)
    {
        // SQL文を閉じる
        $sql = trim($sql);
        if ($sql[strlen($sql)-1] != $this->_sqlEnd) {
            $GLOBALS['forty-two']['FTDB']['sql'][] = $sql . $this->_sqlEnd;
        }
        
        return call_user_func_array('mysql_query', array($sql, $this->_link[$db]['res']));
    }
    
    
    
    /**
     * SQLを実行
     * 
     * SQLを実行します。
     * 
     * @access public
     * @return string
     */
    function query_unbuffered($sql, $db)
    {
        // SQL文を閉じる
        $sql = trim($sql);
        if ($sql[strlen($sql)-1] != $this->_sqlEnd) {
            $GLOBALS['forty-two']['FTDB']['sql'][] = $sql . $this->_sqlEnd;
        }
        
        return call_user_func_array('mysql_unbuffered_query', array($sql, $this->_link[$db]['res']));
    }
    
    
    
    /**
     * DB接続確認処理
     * 
     * DB接続確認を行います。
     * 
     * @access public
     * @return string
     */
    function ping($setting, $db)
    {
        // 接続済みの場合はTRUEを返す
        if (isset($this->_link[$db])
            && $this->_link[$db]['setting'] === $setting
            && call_user_func('mysql_ping', $this->_link[$db]['res'])
        ) {
            return TRUE;
        }
        return FALSE;
    }
    
    
    
    /**
     * SQL文生成処理
     * 
     * SQL文の生成を行います。
     * 
     * @access public
     * @return string
     */
    function _buildSql($sqlType, $param = NULL, $tablename = NULL, $where = NULL)
    {
        if (!isset($this->_baseSQL[$sqlType])) {
            return FALSE;
        }
        
        // 変数の初期化
        $sql = '';
        $baseSQL = $this->_baseSQL[$sqlType];
        
        // テーブル名の挿入
        if ($baseSQL['table']) {
            if($tablename == NULL || !is_string($tablename) || strlen($tablename)<=0) {
                return FALSE;
            }
            $baseSQL['head']    = str_replace('#TABLENAME', '`'.$tablename.'`', $baseSQL['head']);
            $baseSQL['middle']  = str_replace('#TABLENAME', '`'.$tablename.'`', $baseSQL['middle']);
            $baseSQL['tail']    = str_replace('#TABLENAME', '`'.$tablename.'`', $baseSQL['tail']);
        }
        
        // タイプ別生成
        switch ($sqlType) {
            case 'INSERT':
                if (!is_array($param) || count($param)<=0) {
                    return FALSE;
                }
                
                // 整形
                $COLUMNS = sprintf('`%s`', join('`,`', array_keys($param)));
                $VALUES  = sprintf('\'%s\'', join('\',\'', array_values($param)));
                
                // 生成
                $baseSQL['middle']  = str_replace('#COLUMNS', $COLUMNS, $baseSQL['middle']);
                $baseSQL['tail']    = str_replace('#VALUES', $VALUES, $baseSQL['tail']);
                break;
            case 'UPDATE':
                break;
            case 'DELETE':
                break;
            case 'DELETEALL':
                break;
            case 'COUNT':
            case 'SELECT':
                if ((!is_string($param) || strlen($param)<=0) || !is_array($param)) {
                    $param = '*';
                } else if (is_array($param)) {
                    $param = sprintf('`%s`', join('`,`', $param));
                }
                
                $baseSQL['head'] = str_replace('#GETCOLUMN', $param, $baseSQL['head']);
                break;
            case 'SETNAMES':    // 文字コードの設定
                if (!is_string($param)) {
                    return FALSE;
                }
                $baseSQL['head'] = str_replace('#PARAMETER', $param, $baseSQL['head']);
                break;
            default:
                return FALSE;
                break;
        }
        
        // 結合
        $sql .= is_null($baseSQL['head'])   ?'':    $baseSQL['head'];
        $sql .= strlen($sql)>0 ?' ':'';
        $sql .= is_null($baseSQL['middle']) ?'':' '.$baseSQL['middle'];
        $sql .= strlen($sql)>0 ?' ':'';
        $sql .= is_null($baseSQL['tail'])   ?'':' '.$baseSQL['tail'];
        
        // WHERE文の結合
        if ($baseSQL['where'] && $where != NULL && strlen($where)>0) {
            $sql .= ' WHERE '.$where;
        }
        
        $sql = trim($sql);
        
        return $sql;
    }
    
    
    
    /**
     * DB文字コード設定
     * 
     * DB文字コード設定を行います。
     * 
     * @access public
     * @return string
     */
    function _setnames($chara_set, $db)
    {
        if (!isset($this->_charaSet[$chara_set])) {
            return FALSE;
        }
        
        // SQL生成
        $sql = $this->_buildSql('SETNAMES', $this->_charaSet[$chara_set]);
        
        // SQL生成結果
        if ($sql == FALSE) {
            return FALSE;
        }
        
        // 実行
        return $this->query($sql, $db);
    }
    
    
    
    /**
     * テーブル構造情報の取得
     * 
     * 指定DB、テーブル構造情報の取得
     * 
     * @access public
     * @return string
     */
    function _getTableInfo($tableName, $db)
    {
        // テーブル一覧の取得
        $res_tables = $this->query('SHOW TABLES FROM '.$this->_link[$db]['setting']['database'], $db);
        
        $tableInfo = array('primaryKey'=>array());
        while ($table = call_user_func('mysql_fetch_row', $res_tables))
        {
            if ($table[0] != $tableName) {
                continue;
            }
            
            // 引数定義
            $arg = array($this->_link[$db]['setting']['database'], $table[0], $this->_link[$db]['res']);
            
            // フィールド情報の取得し、フィールド件数を取得
            $res = call_user_func_array('mysql_list_fields', $arg);
            $num = call_user_func_array('mysql_num_fields', $res);
            
            // 詳細を取得し保持
            $tableInfo[$table[0]] = $this->_getTableInfoDetail($res, $num, $arg);
            
            // プライマリキーの保存
            if (isset($tableInfo[$table[0]]['primary_key'])&&!is_null($tableInfo[$table[0]]['primary_key'])) {
                $tableInfo['primaryKey'][$table[0]] = $tableInfo[$table[0]]['primary_key'];
                unset($tableInfo[$table[0]]['primary_key']);
            }
            
            // リソース開放
            call_user_func_array('mysql_free_result', array($res));
        }
        // リソース開放
        call_user_func_array('mysql_free_result', array($res_tables));
        
        return $tableInfo;
    }
    
    
    
    /**
     * テーブル構造情報の取得
     * 
     * 指定DB、テーブル構造情報の取得
     * 
     * @access public
     * @return string
     */
    function _getTableInfoDetail($res, $num, $arg)
    {
        $rows = array();
        for ($i = 0 ; $i < $num ; $i++)
        {
            $arg = array($res, $i);
            $name = call_user_func_array('mysql_field_name', $arg);
            $rows[$name]['name'] = $name;
            $rows[$name]['len']  = call_user_func_array('mysql_field_len',  $arg);
            $rows[$name]['type'] = call_user_func_array('mysql_field_type', $arg);
            // blobの時
            $rows[$name]['type'] = ('blob'==$rows[$name]['type'])?'text':$rows[$name]['type'];
            
            // カラムのオプション情報を取得
            $flagsbf = call_user_func_array('mysql_field_flags',  $arg);
            $flags = array_fill_keys(explode(' ', $flagsbf), TRUE);
            $rows[$name]['flags']  = strlen($flagsbf)>0?$flags:array();
            $rows[$name]['flagsbf']= $flagsbf;  // 分割前データの保持(デバッグ時などに利用)
            
            // プライマリキーは分かりやすくする
            if (isset($rows[$name]['flags']['primary_key'])) {
                $rows['primary_key'] = $rows[$name];
            }
        }
        return $rows;
    }
    
    
    
    /**
     * DB接続処理
     * 
     * DB接続処理を行います。
     * 
     * @access private
     * @param array 接続先情報
     * @return boolean
     */
    function _connect($setting, $db)
    {
        // 接続済みの場合はTRUEを返す
        if (isset($this->_link[$db])
            && $this->_link[$db]['setting'] === $setting
            && $this->ping($setting, $db)
        ) {
            return TRUE;
        }
        
        // エラー処理Start
        $error = Error::factory();
        $error->fwLibBegin();

        // 接続先情報作成
        $hostspec = $setting['hostspec'];
        $hostspec .= (isset($setting['port'])&&strlen($setting['port'])>0)?':'.$setting['port']:'';
        $hostspec .= (isset($setting['socket'])&&strlen($setting['socket'])>0)?':'.$setting['socket']:'';

        // 接続情報作成
        $connect[] = $hostspec;
        $connect[] = $setting['username'];
        $connect[] = $setting['password'];

        // 接続
        $res = call_user_func_array('mysql_connect', $connect);

        // エラー処理End
        $error->fwLibEnd();

        // 接続の成否
        if ($res === FALSE) {
            return FALSE;
        }
        
        // DB選択
        call_user_func_array('mysql_select_db', array($setting['database'], $res));
        
        // 接続の保持
        $this->_link[$db] = array('res' => $res, 'setting' => $setting);
        
        return TRUE;
    }
    
    
    
    /**
     * DB接続切断処理
     * 
     * DB接続を切断する処理を行います。
     * 
     * @access private
     * @return string
     */
    function _close($setting, $db)
    {
        // 接続済みでない場合はTRUEを返す
        if (!isset($this->_link[$db])
            || $this->_link[$db]['setting'] !== $setting
            || !$this->ping($setting, $db)
        ) {
            return TRUE;
        }
        
        // エラー処理Start
        $error = Error::factory();
        $error->fwLibBegin();

        // 切断
        $res = call_user_func('mysql_close', $this->_link[$db]['res']);

        // エラー処理End
        $error->fwLibEnd();
        
        // 接続の成否
        if ($res === FALSE) {
            return FALSE;
        }
        
        // リソースの削除
        unset($this->_link[$db]);
        return TRUE;
    }
    
    
    
    /**
     * エラー取得
     * 
     * エラーを取得します。
     * 
     * @access private
     * @return string
     */
    function _getError()
    {
        
    }
    
    
    
    /**
     * エラー詳細メッセージ取得
     * 
     * エラーナンバーからエラー詳細を取得。
     * 
     * @access private
     * @param  string or number  $errorNo エラーナンバー
     * @return string
     */
    function _getDetailedErrorMessage($errorNo = NULL)
    {
        return FALSE;
    }
}
?>